package ua.kpi.flats.service.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import ua.kpi.flats.dao.ApplicationDao;
import ua.kpi.flats.entity.BuyApplication;
import ua.kpi.flats.entity.Contract;
import ua.kpi.flats.entity.SaleApplication;
import ua.kpi.flats.entity.SuitableOption;
import ua.kpi.flats.service.ApplicationService;

@Service("applicationService")
public class ApplicationServiceImpl implements ApplicationService {

	private Logger logger = LoggerFactory.getLogger(ApplicationServiceImpl.class);
	private ApplicationDao applicationDao;
	
	@Transactional(readOnly = true,
			       propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
	public Map<String,Object> getDistricts() {
		try { 
			Map<String,Object> result = new HashMap<String,Object>();
			result.put("districts", applicationDao.getDistricts());			
			return result;			
		} catch (DataAccessException e) {
			logger.error("Cant load districts",e);
			return null;
		}		
	}
	
	@PreAuthorize("hasRole('ROLE_MANAGER')")
	@Transactional(propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
	public boolean addBuy(BuyApplication app) {
		try {
			applicationDao.addBuy(app);
			return true;
		} catch (DataAccessException e) {
			logger.error("Cant add buy application " + app,e);
			return false;
		}
	}
	
	@PreAuthorize("hasRole('ROLE_MANAGER')")
	@Transactional(propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
	public boolean addSale(SaleApplication app) {
		try {
			applicationDao.addFlat(app.getFlat());
			applicationDao.addSale(app);
			return true;
		} catch (DataAccessException e) {
			logger.error("Cant add sale application", e);
			return false;
		}
	}
	
	@PreAuthorize("hasAnyRole('ROLE_MANAGER','ROLE_REALTOR')")
	@Transactional(readOnly = true,
			       propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
	public Map<String,Object> getBuyApplications(int from,int limit) {
		try {
			Map<String,Object> result = new HashMap<String,Object>();
			result.put("applications", applicationDao.getBuyApplications(from, limit));
			result.put("count", applicationDao.buyAppCount());
			return result;
		} catch (DataAccessException e) {
			logger.error("Cant load buy applications from " + from + ",count " + limit,e);
			return null;
		}
	}
	
	@PreAuthorize("hasAnyRole('ROLE_MANAGER','ROLE_REALTOR')")
	@Transactional(readOnly = true,
			       propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
    public Map<String,Object> getSaleApplications(int from, int limit) {
		try {
			Map<String,Object> result = new HashMap<String,Object>();
			result.put("applications", applicationDao.getSaleApplications(from, limit));
			result.put("count", applicationDao.saleAppCount());
			return result;
		} catch (DataAccessException e) {
			logger.error("Cant load sale applications from " + from + ",count " + limit, e);
			return null;
		}
	}

	@PreAuthorize("hasAnyRole('ROLE_MANAGER','ROLE_REALTOR')")
	@Transactional(readOnly = true,
			       propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
    public BuyApplication getBuyApp(long id) {
		try {
			return applicationDao.getBuyApp(id);
		} catch (DataAccessException e) {
			logger.error("Cant load buyapplication with id " + id, e);
			return null;
		}
	}
	
	@PreAuthorize("hasAnyRole('ROLE_MANAGER','ROLE_REALTOR')")
	@Transactional(readOnly = true,
			       propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
    public SaleApplication getSaleApp(long id) {
		try {
			return applicationDao.getSaleApp(id);
		} catch (DataAccessException e) {
			logger.error("Cant load saleapplication with id " + id, e);
			return null;
		}
	}
	
	@PreAuthorize("hasRole('ROLE_MANAGER')")
	@Transactional(propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
    public boolean removeBuyApp(BuyApplication app) {
		try {
			applicationDao.removeBuyApp(app);
            return true;
		} catch (DataAccessException e) {
			logger.error("Cant delete buy application with id " + app.getId(), e);
			return false;
		}
	}
	
	@PreAuthorize("hasRole('ROLE_MANAGER')")
	@Transactional(propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
    public boolean removeSaleApp(SaleApplication app) {
		try {
			applicationDao.removeSaleApp(app);
			return true;
		} catch (DataAccessException e) {
			logger.error("Cant delete sale application with id " + app.getId(), e);
			return false;
		}
	}
	
	@PreAuthorize("hasRole('ROLE_MANAGER')")
	@Transactional(propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED,
			       readOnly = true)
    public Map<String,Object> waitingBuyApps(int from, int limit) {
		try { 
			Map<String,Object> result = new HashMap<String,Object>();
			
			DetachedCriteria excluded = DetachedCriteria.forClass(SuitableOption.class);
			excluded.setProjection(Property.forName("application.id"));
			
			DetachedCriteria criteria = DetachedCriteria.forClass(BuyApplication.class);
			criteria.add(Property.forName("id").notIn(excluded));
			criteria.addOrder(Order.asc("createDate"));
			
			result.put("applications",applicationDao.waitingApps(criteria, from, limit));
			result.put("count", applicationDao.getWaitingAppsCount(criteria));
			
			return result;
		} catch (DataAccessException e) {
			logger.error("Cant load waiting apps from " + from + ",count " + limit, e);
			return null;
		}
	}
	
	@Transactional(propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED,
			       readOnly = true)
    public List<SuitableOption> getOptions(long appId) {
		try {
			return applicationDao.getOptions(appId);
		} catch (DataAccessException e) {
			logger.error("Cant load options for app with id " + appId, e);
			return null;
		}
	}
	
	@Transactional(readOnly = true,
			       propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
    public Map<String,Object> getActiveApps(int from, int limit) {
    	try {
    		Map<String,Object> result = new HashMap<String,Object>();
    		
    		DetachedCriteria excluded = DetachedCriteria.forClass(Contract.class);    		
    		excluded.setProjection(Property.forName("buyApplication.id"));
    		
    		DetachedCriteria included = DetachedCriteria.forClass(SuitableOption.class);
    		included.setProjection(Property.forName("application.id"));
    		
    		DetachedCriteria criteria = DetachedCriteria.forClass(BuyApplication.class);
    		criteria.add(Property.forName("id").notIn(excluded));
    		criteria.add(Property.forName("id").in(included));
    		criteria.addOrder(Order.desc("createDate"));
    		
    		result.put("applications", applicationDao.getActiveBuyApp(criteria, from, limit));
    		result.put("count", applicationDao.activeAppCount(criteria));
    		
    		return result;
    	} catch (DataAccessException e) {
    		logger.error("Cant load active apps from " + from + ",count " + limit, e);
    		return null;
    	}
    }
	
	@Transactional(readOnly = true,
			       propagation = Propagation.REQUIRES_NEW,
			       isolation = Isolation.READ_COMMITTED)
    public SaleApplication getSaleAppByOption(long optionId) {
		try {
			return applicationDao.getSaleAppByOption(optionId);
		} catch (DataAccessException e) {
			logger.error("Cant load sale app by option with id " + optionId, e);
			return null;
		}
	}
	
	@Autowired
	public void setApplicationDao(ApplicationDao applicationDao) {
		this.applicationDao = applicationDao;
	}

	
}
